﻿using System;
using System.Text;
using System.Linq;
using System.Diagnostics;
using System.Reflection;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
using Microsoft.Practices.ServiceLocation;
using Microsoft.Practices.SharePoint.Common;
using Microsoft.Practices.SharePoint.Common.Configuration;
using Microsoft.Practices.SharePoint.Common.ServiceLocation;
using Microsoft.Practices.SharePoint.Common.Logging;

namespace Core.Foundation.Standards
{  
    /// <summary>
    /// This class will provide standard logging practice for  Soltions utilizing ILogger
    /// </summary>
    public sealed class Logging
    {
        public static void LogToULS(string message, Enum enumItem)
        {
            Exception ex = new Exception(message);
            try
            {
                string returnError = string.Empty;
                returnError = TryLogging(ex, enumItem, false);
                if (returnError != string.Empty)
                {
                    throw new Exception(returnError);
                }
            }
            catch
            {
                try
                {
                    DirectEventLog.Log(ex);
                }
                catch
                {
                    throw new Exception("Trace System Failure");
                }
            }
                       
        }

        public static void LogError(Exception senderEX, Enum enumItem)
        {
            try
            {
                // first try to throw as inteneded, using calling enumItem type
                string returnError = string.Empty;
                returnError = TryLogging(senderEX, enumItem);
                if (returnError != string.Empty)
                {
                    // if we failed, before we resort to DirectEventLog entries, try to fail-over to Foundation's Core
                    enumItem = FoundationAppConfig.LoggingCategories.Core;
                    returnError = string.Empty;
                    // create new Exception to throw at Foundation Core, using senderEx as InnerException.
                    Exception coreEX = new Exception("Failover to Foundation Core", senderEX);
                    returnError = TryLogging(coreEX, enumItem);
                    if (returnError != string.Empty)
                    {
                        throw new Exception(returnError);
                    }
                }
            }
            catch (Exception catchEX)
            {
                try
                {                    
                    DirectEventLog.Log(catchEX);
                    DirectEventLog.Log(senderEX);
                }
                catch
                {
                    throw new Exception("Logging System Failure:/n" + catchEX.Message);
                }
            }
           
        }

        private static string TryLogging(Exception ex, Enum enumItem, bool throwError = true)
        {
            string moduleName = string.Empty;
            string attributeValue = string.Empty;
            string category = string.Empty;
            string correlationID = string.Empty;

            try
            {
                Type myClass = enumItem.GetType().DeclaringType;
                
                foreach (FieldInfo field in myClass.GetFields().Where(f => f.Name.Equals("Module")))
                {
                    moduleName = field.GetRawConstantValue().ToString();
                    break;
                }
                
                attributeValue = AppConfig.Attributes.GetAttributeValue<AppConfig.Attributes.AppConfigValueAttribute, string>(enumItem);

                if (moduleName == string.Empty)
                {
                    throw new Exception("Enum Item's base class does not have a constant property named \"Module\"");
                }

                if (attributeValue == string.Empty)
                {
                    throw new Exception("Could not retrive attributeValue");
                }

                category = string.Format("{0}/{1}", moduleName, attributeValue);

                SPSecurity.RunWithElevatedPrivileges(delegate()
                {
                    ILogger log = LogHelper.GetILoggerService();
                    if (throwError)
                    {
                        log.LogToOperations(ex, "Operations Logger", 0, EventSeverity.Error, category);
                    }
                    else
                    {
                        log.TraceToDeveloper(ex.Message, 0, TraceSeverity.Verbose, category);                        
                    }

                });
                return string.Empty;
            }
            catch (Exception catchEx)
            {
                return catchEx.Message;
            }
            
        }

    }

    public sealed class LogHelper
    {

        #region ILogger Instantiation
        /// <summary>
        /// Gets the ILogger Service using Service Locator and current context
        /// </summary>
        /// <returns></returns>
        public static ILogger GetILoggerService()
        {
            IServiceLocator serviceLocator = SharePointServiceLocator.GetCurrent();
            return serviceLocator.GetInstance<ILogger>();
        }
        /// <summary>
        /// Gets the ILogger Service using the SPSite Parameter context
        /// </summary>
        /// <param name="site">SPSite for context</param>
        /// <returns></returns>
        public static ILogger GetILoggerService(SPSite site)
        {

            IServiceLocator serviceLocator = SharePointServiceLocator.GetCurrent(site);
            return serviceLocator.GetInstance<ILogger>();
        }
        #endregion
      
        
        #region Diagnostics Configuration

        /// <summary>
        /// From Microsoft Guidance modified to use our AppConfig  
        /// http://msdn.microsoft.com/en-us/library/ff798462.aspx
        /// </summary>
        public static void SyncConfiguration(Enum @enum, String moduleName)
        {   
            // existing SharePoint Configuration
            IConfigManager configMgr = SharePointServiceLocator.GetCurrent().GetInstance<IConfigManager>();
            DiagnosticsAreaCollection configuredAreas = new DiagnosticsAreaCollection(configMgr);
                      
            // do we already have an existing area?
            DiagnosticsArea existingArea = configuredAreas[moduleName];            
            if (existingArea != null)
            {                
                DiagnosticsCategoryCollection existingcategories = existingArea.DiagnosticsCategories;                
                int catMax = existingcategories.Count;
                for ( int i = 0; i < catMax ; i++ )
                {
                    existingArea.DiagnosticsCategories.RemoveAt(0);
                }
                configuredAreas.SaveConfiguration();
                // remove Area
                if (existingArea.DiagnosticsCategories.Count == 0)
                {
                    configuredAreas.Remove(existingArea);
                }                
                configuredAreas.SaveConfiguration();                
            }

            if (!configuredAreas.Contains(new DiagnosticsArea(moduleName)))
            {
                // new constructs
                DiagnosticsCategoryCollection categories = new DiagnosticsCategoryCollection();
                                
                // our AppConfig items            
                Type type = @enum.GetType();
                string attributeValue = string.Empty;

                foreach (Enum item in Enum.GetValues(type))
                {
                    
                    attributeValue = AppConfig.Attributes.GetAttributeValue<AppConfig.Attributes.AppConfigValueAttribute, string>(item);
                    DiagnosticsCategory categoryItem = new DiagnosticsCategory(attributeValue, EventSeverity.Error, TraceSeverity.Verbose);
                    categories.Add(categoryItem);
                }

                DiagnosticsArea diagnosticArea = new DiagnosticsArea(moduleName, categories);
                configuredAreas.Add(diagnosticArea);
                configuredAreas.SaveConfiguration();

                DiagnosticsAreaEventSource.EnsureConfiguredAreasRegistered();
                
            }
        }

             

        /// <summary>
        ///  From Microsoft Guidance tweaked so to only do one "module" (area)
        ///  http://msdn.microsoft.com/en-us/library/ff798462.aspx
        /// </summary>
        public static void RemoveConfiguration(String moduleName)
        {

            IConfigManager configMgr = SharePointServiceLocator.GetCurrent().GetInstance<IConfigManager>();
            DiagnosticsAreaCollection configuredAreas = new DiagnosticsAreaCollection(configMgr);

            // do we already have an existing area?
            DiagnosticsArea existingArea = configuredAreas[moduleName];
            if (existingArea != null)
            {
                DiagnosticsCategoryCollection existingcategories = existingArea.DiagnosticsCategories;
                int catMax = existingcategories.Count;
                for (int i = 0; i < catMax; i++)
                {
                    existingArea.DiagnosticsCategories.RemoveAt(0);
                }
                configuredAreas.SaveConfiguration();
                // remove Area
                if (existingArea.DiagnosticsCategories.Count == 0)
                {
                    configuredAreas.Remove(existingArea);
                }
                configuredAreas.SaveConfiguration();

                DiagnosticsAreaEventSource.EnsureConfiguredAreasRegistered();
                
            }
        }
        #endregion
    }

    /// <summary>
    /// There are times where we cannot use ILogger, when we don't have context yet, 
    /// or on the feature where we deploy the Area and Categories for ILogger.
    /// The executable also does not have context so we're going to write to the EventLog.
    /// </summary>
    public class DirectEventLog
    {
        private const string sourceConfig = "Core/Direct EventLog";
        
        public static void EnsureEventLogSource()
        {
            if (!EventLog.SourceExists(sourceConfig))
            {
                EventLog.CreateEventSource(sourceConfig, "Application");
            }
        }

        /// <summary>
        /// GetSource returns the Area/Category for logging for the module
        /// </summary>
        private static string GetSource()
        {            
            return sourceConfig;
        }

        /// <summary>
        /// Throws the exception to the log, calls SetupEventLog prior to the WriteEntry to insure
        /// we have a Source setup. We also use the BuildExceptionMessage to build the message.
        /// </summary>
        /// <param name="ex"></param>
        public static void Log(Exception ex)
        {
            SPSecurity.RunWithElevatedPrivileges(delegate()
            {
                EventLog.WriteEntry(DirectEventLog.GetSource(), DirectEventLog.BuildExceptionMessage(ex),
                    EventLogEntryType.Error);
            });
        }


        #region Pulled from the Microsoft Practice code to ensure similiar logging

        /// <summary>
        /// Build an exception message for an exception that contains more information than the
        /// normal Exception.ToString().
        /// </summary>
        /// <param name="exception">The exception to format.</param>
        /// <param name="customErrorMessage">Any custom error information to include.</param>
        /// <returns>The error message.</returns>
        private static string BuildExceptionMessage(Exception exception, string customErrorMessage = "")
        {
            StringBuilder messageBuilder = new StringBuilder();

            if (!string.IsNullOrEmpty(customErrorMessage))
            {
                messageBuilder.AppendLine(customErrorMessage);
            }
            else
            {
                messageBuilder.AppendLine("An exception has occurred.");
            }

            WriteExceptionDetails(messageBuilder, exception, 1);


            return messageBuilder.ToString();
        }

        /// <summary>
        /// Write the details of an exception to the string builder. This method is called recursively to 
        /// format all the inner exceptions. 
        /// </summary>
        /// <param name="messageBuilder">The stringbuilder that will hold the full exception message.</param>
        /// <param name="exception">The exception (and all it's inner exceptions) to add.</param>
        /// <param name="level">How far should the exceptions be indented.</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "1")]
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0")]
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2233:OperationsShouldNotOverflow", MessageId = "level+1", Justification = "Not an issue.")]
        private static void WriteExceptionDetails(StringBuilder messageBuilder, Exception exception, int level)
        {
            Validation.ArgumentNotNull(messageBuilder, "messageBuilder");
            Validation.ArgumentNotNull(exception, "exception");

            int nextLevel = level + 1;

            messageBuilder.AppendFormat("{0}ExceptionType: '{1}'\r\n", Indent(level), exception.GetType().Name);
            messageBuilder.AppendFormat("{0}ExceptionMessage: '{1}'\r\n", Indent(level),
                                        EnsureIndentation(exception.Message, level));
            messageBuilder.AppendFormat("{0}StackTrace: '{1}'\r\n", Indent(level),
                                        EnsureIndentation(exception.StackTrace, level));
            messageBuilder.AppendFormat("{0}Source: '{1}'\r\n", Indent(level),
                                        EnsureIndentation(exception.Source, level));

            try
            {
                messageBuilder.AppendFormat("{0}TargetSite: '{1}'\r\n", Indent(level),
                                                EnsureIndentation(exception.TargetSite, level));
            }
            catch (TypeLoadException)
            {
                // in some cases exceptions may originate from a proxy in full trust with types
                // that are not allowed in the sandbox.  In this case a TypeLoadException will be
                // thrown.  We do not want to mask the original error if this gets thrown.
            }



            if (exception.Data != null && exception.Data.Count > 0)
            {
                messageBuilder.AppendLine(Indent(level) + "Additional Data:");
                foreach (string key in exception.Data.Keys)
                {
                    WriteAdditionalExceptionData(level, messageBuilder, exception, key, nextLevel);
                }
            }

            if (exception.InnerException != null)
            {
                messageBuilder.AppendLine(Indent(level) + "------------------------------------------------------------");
                messageBuilder.AppendLine(Indent(level) + "Inner exception:");
                messageBuilder.AppendLine(Indent(level) + "------------------------------------------------------------");
                WriteExceptionDetails(messageBuilder, exception.InnerException, nextLevel);
            }
        }

        private static void WriteAdditionalExceptionData(int level, StringBuilder messageBuilder, Exception exception,
                                                  string key, int nextLevel)
        {
            object value = exception.Data[key];
            if (value != null)
            {
                Exception valueAsException = value as Exception;
                if (valueAsException != null)
                {
                    messageBuilder.AppendFormat("{0}{1} is an exception. Exception Details:\r\n", Indent(nextLevel), key);
                    WriteExceptionDetails(messageBuilder, valueAsException, nextLevel + 1);
                }
                else
                {
                    messageBuilder.AppendFormat("{0}'{1}' : '{2}'\r\n", Indent(nextLevel), key,
                                                EnsureIndentation(value, level));
                }
            }
        }

        private static string EnsureIndentation(object obj, int indentationLevel)
        {
            if (obj == null)
                return string.Empty;

            return obj.ToString().Replace("\n", "\n" + Indent(indentationLevel + 1));
        }

        private static string Indent(int indentationLevel)
        {
            StringBuilder builder = new StringBuilder();
            for (int i = 0; i < indentationLevel; i++)
            {
                builder.Append("\t");
            }
            return builder.ToString();
        }
        #endregion
    }



 
}

